package com.tool.emulator.mos6502.asm;

import com.tool.emulator.apple2.Util;
import com.tool.emulator.mos6502.instrs.AddressingMethod;
import com.tool.emulator.mos6502.instrs.Instr;

import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

/**
 * ASM652.java @author tl 2011-6-28
 */
public class ASM6502 {
	public static final String DFB = "DFB";
	public static final String DB = "DB";
	public static final String DATA = "DATA";
	public static final String DEF = "DEF";
	static final Value[] EMPTY = new Value[0];
	protected static HashMap<String, ArrayList<Instr>> instrMap = new HashMap<String, ArrayList<Instr>>();

	static {
		for (Instr instr : Instr.instrs) {
			if (instr != null && instr.code < 0x100) {
				ArrayList<Instr> list = instrMap.get(instr.name);
				if (list == null) {
					list = new ArrayList<Instr>();
					instrMap.put(instr.name, list);
				}
				list.add(instr);
			}
		}
	}

	protected final HashMap<String, Symbol> symbols = new HashMap<String, Symbol>();
	protected final ArrayList<Line> lines = new ArrayList<Line>();
	protected Line current = null;
	protected int error_count = 0;
	protected Map<String, Number> vars = null;

	static final Value valueOf(Value v, int w) {
		int vw = v.width();
		if (vw == w) return v;
		if (vw > w) return null;
		return new RefVal(v, w);
	}

	static Value getConst(String s) {
		try {
			int val = Integer.parseInt(s, 16);
			if (s.length() <= 2)
				return new ConstVal(val, 1);
			if (s.length() <= 4)
				return new ConstVal(val, 2);
		} catch (Exception e) {
			System.err.println(e);
		}
		return null;
	}

	protected static boolean match(String s, String... strings) {
		for (String s1 : strings) {
			if (s.equals(s1))
				return true;
		}
		return false;
	}

	public static ASM6502 compile(String[] lines) {
		return compile(lines, 1);
	}

	public static ASM6502 compile(File file) throws IOException {
		BufferedReader in = new BufferedReader(new FileReader(file));
		try {
			ASM6502 asm = new ASM6502();
			for (int ln = 1; ; ln++) {
				String line = in.readLine();
				if (line == null)
					break;
				asm.parseLine(ln, line);
			}
			return asm;
		} finally {
			in.close();
		}
	}

	public static ASM6502 compile(String[] lines, int startLineNumber) {
		ASM6502 asm = new ASM6502();
		for (int i = 0; i < lines.length; i++) {
			asm.parseLine(startLineNumber + i, lines[i]);
		}
		return asm;
	}

	public static void main(String[] args) throws Exception {
		File file = new File("test.asm");
		File file1 = new File("test.asm.txt");
		ASM6502 asm = compile(file);
		byte[] bytes = asm.build(null, 0x300);
		if (bytes != null) {
			Util.dumpBytes(0x300, bytes);
			PrintWriter out = new PrintWriter(file1);
			out.println("CALL -151");
			Util.dumpBytes(out, 0x300, bytes);
			out.println("300L");
			out.close();
		}
	}

	protected void error(Line line, String msg) {
		if (line != null)
			System.err.println("Line(" + line.number + ") " + line.text);
		System.err.println("\t" + msg);
		error_count++;
	}

	Value getVar(String name) {
		if (vars == null) return null;
		Number n = vars.get(name);
		if (n == null)
			return null;
		if (n instanceof Byte) {
			return new ConstVal(n.intValue(), 1);
		}
		return new ConstVal(n.intValue(), 2);
	}

	protected Symbol getSymbol(String name) {
		if (name.startsWith("$"))
			name = name.substring(1);
		Symbol s = symbols.get(name);
		if (s == null) {
			s = new Symbol(this.current, name);
			symbols.put(name, s);
		}
		return s;
	}

	protected void parseLine(int lineNumber, String line) {
		String line0 = line;
		line = line.replace('\t', ' ').trim().toUpperCase();
		int i = line.indexOf(';');
		if (i >= 0) line = line.substring(0, i).trim();
		if (line.length() == 0)
			return;
		Line cur = this.current = new Line(lineNumber, line0);
		lines.add(cur);

		i = line.indexOf(':');
		if (i >= 0) {
			String s = line.substring(0, i).trim();
			if (s.length() > 0) {
				cur.label = getSymbol(s);
				cur.label.setLabel(cur);
			}
			line = line.substring(i + 1).trim();
		}
		if (line.length() > 0) {
			i = line.indexOf(' ');
			if (i < 0)
				cur.stm = line;
			else {
				cur.stm = line.substring(0, i).trim();
				cur.param = line.substring(i + 1).trim();
			}
		}
		if (cur.param != null && cur.param.length() == 0)
			cur.param = null;
	}

	protected void parseStm(Line cur) {
		if (match(cur.stm, DFB, DB, DATA))
			parseData(cur);
		else if (match(cur.stm, DEF))
			parseDef(cur);
		else {
			ArrayList<Instr> list = instrMap.get(cur.stm);
			if (list == null || list.size() == 0)
				error(cur, "unkown instr");
			else
				parseInstr(cur, list);
		}
	}

	protected void parseDef(Line cur) {
		if (cur.param == null)
			error(cur, "invalid statement");
		else {
			String[] ss = cur.param.split(" ");
			if (ss.length != 0)
				error(cur, "invalid statement");
			else {
				Symbol s = getSymbol(ss[0]);
				Symbol v = getSymbol(ss[1]);
				s.setValue(v);
			}
		}
	}

	protected void parseData(Line cur) {
		String[] ss = cur.param == null ? new String[0] : cur.param.split(",");
		int len = 0;
		Value[] vals = new Value[ss.length];
		for (int i = 0; i < vals.length; i++) {
			String s = ss[i].trim();
			if (s.length() == 0) {
				error(cur, "two ','");
				vals[i] = this.getSymbol("00");
			} else
				vals[i] = this.getSymbol(s);
			len += vals[i].width();
		}
		cur.len = len;
		cur.values = vals;
	}

	protected void parseStms(int addr) {
		for (Line x : this.lines) {
			this.current = x;
			x.addr = addr;
			if (x.stm != null && x.stm.length() > 0)
				this.parseStm(x);
			addr += x.len;
		}
	}

	protected Instr getInstr(ArrayList<Instr> list, AddressingMethod md) {
		for (Instr x : list) {
			if (x.addressingMethod == md)
				return x;
		}
		error(null, "invalid addressing method");
		return null;
	}

	protected Value[] parseValue(String s, final int w) {
		final Symbol x = this.getSymbol(s);
		if (x.width() > w) return null;
		return new Value[]{
				new Value() {
					@Override
					int value() {
						return x.value();
					}

					@Override
					int width() {
						return w;
					}
				}
		};
	}

	protected Value[] parseValue(String s, String left, String right, int w) {
		s = mathLeft(s, left);
		s = mathRight(s, right);
		if (s == null) return null;
		return parseValue(s, w);

	}

	protected String mathLeft(String s, String left) {
		if (s == null) return null;
		s = s.trim();
		while (left != null && left.length() > 0) {
			String s1 = left.substring(0, 1);
			if (!s.startsWith(s1))
				return null;
			left = left.substring(1).trim();
			s = s.substring(1).trim();
		}
		return s;
	}

	protected String mathRight(String s, String right) {
		if (s == null) return null;
		s = s.trim();
		while (right != null && right.length() > 0) {
			String s1 = right.substring(right.length() - 1, right.length());
			if (!s.endsWith(s1))
				return null;
			right = right.substring(0, right.length() - 1).trim();
			s = s.substring(0, s.length() - 1).trim();
		}
		return s;
	}

	protected Value[] parseRelative(String s) {
		final Value[] vals = parseValue(s, 2);
		if (vals == null) return null;
		final Line line = this.current;
		return new Value[]{
				new Value() {
					@Override
					int value() {
						int diff = vals[0].value() - (line.addr + 2);
						if (diff > 127 || diff < -128) {
							error(line, "relative addr to far " + diff);
						}
						return diff;
					}

					@Override
					int width() {
						return 1;
					}
				}
		};
	}

	protected Value[] parseInstr1(Line cur, Instr instr) {
		String param = cur.param;
		if (instr.addressingMethod == AddressingMethod.Accumulator || instr.addressingMethod == AddressingMethod.Implied) {
			return param == null ? EMPTY : null;
		}
		if (param == null) return null;
		switch (instr.addressingMethod) {
			case Immediate:
				if (!param.startsWith("#"))
					return null;
				return parseValue(param.substring(1), 1);
			case Zero_Page:
				return parseValue(param, 1);
			case Absolute:
				return parseValue(param, 2);
			case Zero_Page_X: //XX,X
				return parseValue(param, null, ",X", 1);
			case Absolute_X:
				return parseValue(param, null, ",X", 2);
			case Zero_Page_Y:
				return parseValue(param, null, ",Y", 1);
			case Absolute_Y:
				return parseValue(param, null, ",Y", 2);
			case Relative:
				return parseRelative(param);
			case Indirect_X:
				return parseValue(param, "(", ",X)", 1);
			case Indirect_Y:
				return parseValue(param, "(", "),Y", 1);
			case Indirect:
				return parseValue(param, "(", ")", 2);
		}
		return null;
	}

	protected void parseInstr(Line cur, ArrayList<Instr> list) {
		if (cur.param != null && cur.param.length() == 0)
			cur.param = null;
		Instr mininstr = null;
		Value[] minVals = null;
		for (Instr x : list) {
			Value[] vals = parseInstr1(cur, x);
			if (vals != null && (minVals == null || minVals.length > vals.length)) {
				mininstr = x;
				minVals = vals;
				if (vals.length == 0)
					break;
			}
		}
		if (mininstr == null) {
			error(cur, "invalid addressing method");
			mininstr = list.get(0);
		}
		cur.instr = mininstr;
		cur.values = minVals;
		cur.len = mininstr.opLen;
	}

	public byte[] build(Map<String, Number> vars, int address) {
		this.parseStms(address);
		if (this.getErrorCount() > 0)
			return null;
		this.vars = vars;
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		for (Line x : this.lines) {
			x.write(out);
		}
		if (this.getErrorCount() > 0)
			return null;
		return out.toByteArray();
	}

	public int getErrorCount() {
		return this.error_count;
	}

	protected static abstract class Value {
		abstract int value();

		abstract int width();
	}

	static final class RefVal extends Value {
		final Value val;
		final int w;

		private RefVal(Value val, int w) {
			super();
			this.val = val;
			this.w = w;
		}

		@Override
		int value() {
			return val.value();
		}

		@Override
		int width() {
			return w;
		}

	}

	static final class ConstVal extends Value {
		final int val, w;

		ConstVal(int val, int w) {
			super();
			this.val = val;
			this.w = w;
		}

		@Override
		int value() {
			return val;
		}

		@Override
		int width() {
			return w;
		}

	}

	protected class Symbol extends Value {
		final String name;
		final Line decl;
		Line labelOf;
		Value value;

		Symbol(Line decl, String name) {
			super();
			this.name = name;
			this.decl = decl;
		}

		void setLabel(Line line) {
			if (this.value != null)
				error(line, "this is a constants value");
			if (this.labelOf != null)
				error(line, "label duplicate");
			else
				this.labelOf = line;
		}

		void setValue(Value value) {
			if (this.labelOf != null)
				error(decl, "this is a label,can't be a value");
			this.value = value;
		}

		Value getValue(boolean err) {
			if (labelOf != null)
				return new ConstVal(labelOf.addr, 2);
			if (this.value != null)
				return this.value;
			Value n = getConst(name);
			if (n == null && vars != null) n = getVar(name);
			if (n == null && err)
				error(decl, "undefined symbol " + name);
			return n;
		}

		@Override
		int value() {
			Value val = getValue(true);
			return val == null ? 0 : val.value();
		}

		@Override
		int width() {
			if (this.labelOf != null)
				return 2;
			Value val = getValue(false);
			return val == null ? 2 : val.width();
		}

	}

	protected class Line {
		final int number;
		final String text;
		Symbol label;
		String stm;
		String param;
		Instr instr;
		Value[] values;
		int addr;
		int len;

		Line(int number, String text) {
			super();
			this.number = number;
			this.text = text;
		}

		void write(ByteArrayOutputStream out) {
			int size0 = out.size();
			if (instr != null)
				out.write(instr.code);
			if (values != null) {
				for (Value v : values) {
					if (v != null) {
						int w = v.width();
						if (w > 0) {
							int val = v.value();
							if (w == 1)
								out.write(val);
							else if (w == 2) {
								out.write(val);
								out.write(val >> 8);
							} else
								error(this, "invalid data width " + w);
						}
					}
				}
			}
			int size1 = out.size();
			if (size1 - size0 != len)
				error(this, "invalid data len " + (size1 - size0) + " != " + len);
		}
	}
}
